package com.gitee.dubboOps;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.utils.ReferenceConfigCache;
import com.alibaba.dubbo.rpc.service.GenericService;
import com.gitee.dubboOps.Utils.GenericInvokeUtils;
import com.gitee.dubboOps.Utils.MethodSignatureUtils;
import com.gitee.dubboOps.config.ZkListener;
import com.gitee.dubboOps.model.ServiceDO;
import com.gitee.dubboOps.param.ServiceInvokeParam;
import com.gitee.dubboOps.param.ServiceInvokeResult;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.curator.framework.CuratorFramework;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Created by jiuguo 20181213
 * https://www.cnblogs.com/lobo/p/7129119.html
 */
@Service
public class DubboServiceFactory {

    private ApplicationConfig application;

    private RegistryConfig registry;

    private String targetProject;

    @Value("${dubbo.registry.address}")
    private String zkUrl;


    @Value("${spring.application.name}")
    private String applicatonName;

    @Autowired
    private CuratorFramework client;

//    private static class SingletonHolder {
//        private static DubboServiceFactory INSTANCE = new DubboServiceFactory();
//    }

    private DubboServiceFactory(@Value("${dubbo.registry.address}") String zkUrl, @Value("${spring.application.name}") String applicatonName){
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName(applicatonName);
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("zookeeper://" + zkUrl);

        this.application = applicationConfig;
        this.registry = registryConfig;
    }

    //根据项目标签选择一个目标
    private ServiceDO findTarget4project(List<ServiceDO> serviceDOS, String project) {
        for(ServiceDO serviceDO: serviceDOS) {
            if(serviceDO.getZone().equalsIgnoreCase(project)) {
                this.targetProject = project;
                return serviceDO;
            }
        }

        return null;
    }

    //根据IP选择一个目标
    private ServiceDO findTargetByIp(List<ServiceDO> serviceDOS, String ip) {
        for(ServiceDO serviceDO: serviceDOS) {
            if(serviceDO.getIP().equalsIgnoreCase(ip)) {
                return serviceDO;
            }
        }

        return null;
    }


    public ServiceInvokeResult invoke(ServiceInvokeParam invokeParam) throws Exception {
        ServiceInvokeResult result = new ServiceInvokeResult();

        final String envType = invokeParam.getEnvType(); //暂不考虑环境
        final String dataId = invokeParam.getDataId();
        final String methodSignature = invokeParam.getMethodSignature();
        final Object[] invokeArgs = invokeParam.getInvokeArgs();
        String targetIp = invokeParam.getTargetIp();
        final boolean convertInternalClass = invokeParam.isConvertInternalClass();

        List<ServiceDO> targets = null;

        if(StringUtils.isEmpty(targetIp) && client!=null) { //未指定targetIp时，走配置中心
            // 获取服务提供方列表
            targets = ZkListener.findProviderTargetsByDataId(client, dataId);
            if (targets == null || targets.isEmpty()) {
                result.setSuccess(false);
                result.setErrorMsg("cannot find any provider for env=[" + envType + "], dataId=[" + dataId + "]");
                return result;
            }
        }


        ServiceDO target = null;
        // 根据用户指定的 targetIp 筛选 target
//        if (targetIp != null) {
//            target = findTargetByIp(targets, targetIp);
//            if (target == null) {
//                result.setSuccess(false);
//                result.setErrorMsg("target ip [" + targetIp + "] is not in provider list of service: " + dataId
//                    + ", therefore cannot invoke its methods");
//                return result;
//            }
//        }

        String project = invokeParam.getTargetProject();
        if(project !=null && StringUtils.isNotEmpty(project)) { //根据项目标签查找目标
            target = findTarget4project(targets, project);
            if (target == null) {
                result.setSuccess(false);
                result.setErrorMsg("target project [" + project + "] is not in provider list of service: " + dataId
                        + ", therefore cannot invoke its methods");
                return result;
            }
        }

        try {
            ReferenceConfig<GenericService> reference = new ReferenceConfig<GenericService>();
            reference.setApplication(application);
            reference.setInterface(dataId); // 接口名
            reference.setGeneric(true); // 声明为泛化接口

            String directUrl = "";
            if(StringUtils.isNotEmpty(targetIp)) { //直连，忽略注册中心，不再做目标provider有效性判断
                directUrl = "dubbo://" + targetIp;
            }else if(targets!=null) { // 走注册中心
                reference.setRegistry(registry);
                target = targets.get(0);
                directUrl = "dubbo://" + target.getIP() + ":" + target.getPort();
            }else {
                result.setSuccess(false);
                result.setErrorMsg("target is not in provider list of service: " + dataId
                        + ", therefore cannot invoke its methods");
                return result;
            }

            reference.setUrl(directUrl);
            result.setInvokeTarget(directUrl.replace("dubbo://", ""));
        //    result.setTargetProject(target.getZone());

            if(StringUtils.isNotEmpty(invokeParam.getVersion())) {
                reference.setVersion(invokeParam.getVersion());
            }

            if(invokeParam.getCustomTimeout()>0) {
                reference.setTimeout(invokeParam.getCustomTimeout());
            }else {
                reference.setTimeout(target.getTimeout());
            }

            //ReferenceConfig实例很重，封装了与注册中心的连接以及与提供者的连接，
            //需要缓存，否则重复生成ReferenceConfig可能造成性能问题并且会有内存和连接泄漏。
            //API方式编程时，容易忽略此问题。
            //这里使用dubbo内置的简单缓存工具类进行缓存
            ReferenceConfigCache cache = ReferenceConfigCache.getCache();
            GenericService genericService = cache.get(reference);
            if (genericService==null) {
                genericService = reference.get();
            }

            // 执行调用
            Object invokeResult = genericService.$invoke(MethodSignatureUtils.findMethodName(methodSignature),
                    convertParameterTypes(methodSignature, convertInternalClass),
                    convertParameterValues(invokeArgs, convertInternalClass));
            result.setInvokeResult(invokeResult);
            result.setSuccess(true);
        } catch (Exception e) {
            result.setSuccess(false);
            result.setErrorMsg("fail to invoke [" + methodSignature + "] on [" + dataId + "] because of "
                + ExceptionUtils.getStackTrace(e));
        }

        return  result;
    }

    private String[] convertParameterTypes(String methodSignature, boolean convertInternalClass) {
        String[] types = MethodSignatureUtils.findParameterTypes(methodSignature);
        if (types != null) {
            for (int i = 0; i < types.length; i++) {
                types[i] = MethodSignatureUtils.eraseGenericType(types[i]);
                if (convertInternalClass) {
                    types[i] = MethodSignatureUtils.convertInternalClassStr(types[i]);
                }
            }
        }
        return types;
    }

    private Object[] convertParameterValues(Object[] parameters, boolean convertInternalClass) {
        return convertInternalClass ? GenericInvokeUtils.convertInternalClass(parameters) : parameters;
    }

}